package tech.xs.sys.service.impl;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RReadWriteLock;
import org.redisson.api.RedissonClient;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import tech.xs.common.framework.domain.constant.Symbol;
import tech.xs.common.framework.domain.entity.BaseEntity;
import tech.xs.common.framework.domain.exception.BaseException;
import tech.xs.common.framework.domain.model.ApiResult;
import tech.xs.common.framework.service.CrudServiceImpl;
import tech.xs.common.framework.util.BaseEntityUtil;
import tech.xs.common.framework.util.DictUtil;
import tech.xs.sys.dao.SysChildDictDao;
import tech.xs.sys.dao.SysChildDictDataDao;
import tech.xs.sys.dao.SysDictDao;
import tech.xs.sys.dao.SysDictDataDao;
import tech.xs.sys.domain.cache.DictCache;
import tech.xs.sys.domain.cache.DictDataCache;
import tech.xs.sys.domain.entity.SysChildDict;
import tech.xs.sys.domain.entity.SysChildDictData;
import tech.xs.sys.domain.entity.SysDict;
import tech.xs.sys.domain.entity.SysDictData;
import tech.xs.sys.service.SysDictService;

import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * SysDictServiceImpl
 *
 * @author 沈家文
 * @since 2023/5/10 15:21
 */
@Slf4j
@Service
public class SysDictServiceImpl extends CrudServiceImpl<SysDictDao, SysDict> implements SysDictService {

    /**
     * 字典锁的缓存前缀
     * <p> 针对单个字典
     *
     * @since 2023/5/10 14:42
     */
    private static final String LOCK_DICT_CACHE_KEY = "sys:lock:dict:cache:";


    /**
     * 字典缓存前缀
     * <p> 针对单个字典
     *
     * @since 2023/5/10 14:43
     */
    private static final String CACHE_DICT_KEY = "sys:dict:cache:";

    @Resource
    private RedissonClient redissonClient;
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Resource
    private SysDictDao sysDictDao;
    @Resource
    private SysChildDictDao sysChildDictDao;
    @Resource
    private SysChildDictDataDao sysChildDictDataDao;
    @Resource
    private SysDictDataDao sysDictDataDao;
    @Resource
    private ObjectMapper objectMapper;


    @Override
    public String getDictCache(String dictCode) {
        RLock lock = getLoadDicCachetReadLock(dictCode);
        try {
            return stringRedisTemplate.opsForValue().get(CACHE_DICT_KEY + dictCode);
        } finally {
            lock.unlock();
        }
    }

    @Override
    public void reloadAllDictCache() {
        List<SysDict> dictList = sysDictDao.selectList(Wrappers.<SysDict>lambdaQuery().select(BaseEntity::getId, SysDict::getCode));
        Map<Long, SysDict> dictMap = BaseEntityUtil.toIdMap(dictList);
        List<SysChildDict> childDictList = sysChildDictDao.selectList(Wrappers.<SysChildDict>lambdaQuery().select(SysChildDict::getDictId, SysChildDict::getCode));
        for (SysChildDict childDict : childDictList) {
            SysDict dict = dictMap.get(childDict.getDictId());
            if (dict == null) {
                throw new BaseException("加载字典缓存失败 通过子字典关联字典失败 childDict:" + childDict);
            }
            List<SysChildDict> childDicts = dict.getChildDicts();
            if (childDicts == null) {
                childDicts = new ArrayList<>();
                dict.setChildDicts(childDicts);
            }
            childDicts.add(childDict);
        }
        setAllDictCache(dictList);
    }

    private void setAllDictCache(List<SysDict> dictList) {
        Set<String> keys = stringRedisTemplate.keys(CACHE_DICT_KEY + Symbol.ASTERISK);
        if (keys != null) {
            for (SysDict sysDict : dictList) {
                keys.remove(CACHE_DICT_KEY + sysDict.getCode());
                List<SysChildDict> childDicts = sysDict.getChildDicts();
                if (childDicts == null) {
                    continue;
                }
                for (SysChildDict childDict : childDicts) {
                    keys.remove(CACHE_DICT_KEY + sysDict.getCode() + Symbol.COLON + childDict.getCode());
                }
            }
            stringRedisTemplate.delete(keys);
        }
        for (SysDict item : dictList) {
            reloadDictCache(item.getCode());
        }
    }

    @Override
    @SneakyThrows
    public void reloadDictCache(String dictCode) {
        if (StrUtil.isBlank(dictCode)) {
            return;
        }
        SysDict dict = sysDictDao.selectOne(Wrappers.<SysDict>lambdaQuery().eq(SysDict::getCode, dictCode));
        RLock lock = getLoadDictCacheWriteLock(dictCode);
        try {
            if (dict == null) {
                stringRedisTemplate.delete(CACHE_DICT_KEY + dictCode);
                stringRedisTemplate.delete(stringRedisTemplate.keys(CACHE_DICT_KEY + dictCode + Symbol.COLON + Symbol.ASTERISK));
            } else {
                DictCache dictCache = new DictCache();
                dictCache.setCode(dict.getCode());
                dictCache.setName(dict.getName());
                List<SysDictData> dictDataList = sysDictDataDao.selectList(Wrappers.<SysDictData>lambdaQuery().eq(SysDictData::getDictId, dict.getId()));
                List<DictDataCache> dictDataCaches = new ArrayList<>();
                for (SysDictData dictData : dictDataList) {
                    DictDataCache dictDataCache = new DictDataCache();
                    dictDataCache.setCode(DictUtil.getCodeValue(dict.getValueType(), dictData.getCode()));
                    dictDataCache.setName(dictData.getName());
                    dictDataCache.setOrderValue(dictData.getOrderValue());
                    dictDataCache.setStatus(dictData.getStatus());
                    dictDataCaches.add(dictDataCache);
                }
                dictDataCaches.sort(Comparator.comparing(DictDataCache::getOrderValue));
                dictCache.setDatas(dictDataCaches);
                log.info("加载字典缓存 dictCode:{} cache:{}", dictCache.getCode(), dictCache);
                stringRedisTemplate.opsForValue().set(CACHE_DICT_KEY + dictCode, dictToCacheValue(dictCache));
            }
        } catch (Exception e) {
            log.error("加载字典缓存异常 dictCode:" + dictCode, e);
            throw e;
        } finally {
            lock.unlock();
        }
        if (dict != null) {
            List<SysChildDict> childDicts = sysChildDictDao.selectList(Wrappers.<SysChildDict>lambdaQuery().select(SysChildDict::getCode).eq(SysChildDict::getDictId, dict.getId()));
            for (SysChildDict sysChildDict : childDicts) {
                reloadChildDictCache(dictCode, sysChildDict.getCode());
            }
        }
    }

    @Override
    public void reloadChildDictCache(String dictCode, String childDictCode) {
        if (StrUtil.isBlank(dictCode) || StrUtil.isBlank(childDictCode)) {
            return;
        }
        SysDict dict = sysDictDao.selectOne(Wrappers.<SysDict>lambdaQuery().eq(SysDict::getCode, dictCode));
        String cacheKey = CACHE_DICT_KEY + dictCode + Symbol.COLON + childDictCode;
        RLock lock = getLoadDictCacheWriteLock(dictCode + Symbol.COLON + childDictCode);
        try {
            if (dict == null) {
                stringRedisTemplate.delete(cacheKey);
                return;
            }
            SysChildDict childDict = sysChildDictDao.selectOne(Wrappers.<SysChildDict>lambdaQuery()
                    .eq(SysChildDict::getDictId, dict.getId())
                    .eq(SysChildDict::getCode, childDictCode)
            );
            if (childDict == null) {
                stringRedisTemplate.delete(cacheKey);
                return;
            }
            List<SysDictData> dictDataList = sysDictDataDao.selectList(Wrappers.<SysDictData>lambdaQuery().eq(SysDictData::getDictId, dict.getId()));
            Map<Long, SysDictData> dictDataMap = BaseEntityUtil.toIdMap(dictDataList);
            List<SysChildDictData> childDictDataList = sysChildDictDataDao.selectList(Wrappers.<SysChildDictData>lambdaQuery().eq(SysChildDictData::getChildDictId, childDict.getId()));
            List<DictDataCache> dictDataCaches = new ArrayList<>();
            for (SysChildDictData childDictData : childDictDataList) {
                DictDataCache dictDataCache = new DictDataCache();
                SysDictData dictData = dictDataMap.get(childDictData.getDictDataId());
                dictDataCache.setCode(DictUtil.getCodeValue(dict.getValueType(), dictData.getCode()));
                dictDataCache.setName(childDictData.getName());
                dictDataCache.setOrderValue(childDictData.getOrderValue());
                dictDataCache.setStatus(childDictData.getStatus());
                dictDataCaches.add(dictDataCache);
            }
            DictCache dictCache = new DictCache();
            dictCache.setCode(dictCode + Symbol.COLON + childDictCode);
            dictCache.setName(childDict.getName());
            dictDataCaches.sort(Comparator.comparing(DictDataCache::getOrderValue));
            dictCache.setDatas(dictDataCaches);
            log.info("加载子字典缓存 dictCode:{} childDictCode:{} cache:{}", dict.getCode(), childDictCode, dictCache);
            stringRedisTemplate.opsForValue().set(CACHE_DICT_KEY + dictCode + Symbol.COLON + childDictCode, dictToCacheValue(dictCache));
        } catch (Exception e) {
            log.error("加载子字典缓存异常 dictCode:" + dictCode + " childDictCode:" + childDictCode, e);
            throw e;
        } finally {
            lock.unlock();
        }
    }

    @Override
    public void deleteDictCache(String dictCode) {
        RLock lock = getLoadDictCacheWriteLock(dictCode);
        try {
            stringRedisTemplate.delete(CACHE_DICT_KEY + dictCode);
            stringRedisTemplate.delete(stringRedisTemplate.keys(CACHE_DICT_KEY + dictCode + Symbol.COLON + Symbol.ASTERISK));
        } finally {
            lock.unlock();
        }
    }

    @Override
    public void deleteChildDictCache(String dictCode, String childDictCode) {
        RLock lock = getLoadDictCacheWriteLock(dictCode + Symbol.COLON + childDictCode);
        try {
            stringRedisTemplate.delete(CACHE_DICT_KEY + dictCode + Symbol.COLON + childDictCode);
        } finally {
            lock.unlock();
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteDictByCode(String dictCode) {
        SysDict dict = sysDictDao.selectOne(Wrappers.<SysDict>lambdaQuery().select(BaseEntity::getId).eq(SysDict::getCode, dictCode));
        if (dict == null) {
            return;
        }
        deleteDictById(dict.getId(), dictCode);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteDictById(Long dictId) {
        SysDict dict = sysDictDao.selectOne(Wrappers.<SysDict>lambdaQuery().select(SysDict::getCode).eq(BaseEntity::getId, dictId));
        deleteDictById(dictId, dict.getCode());
    }


    /**
     * 删除单个字典数据
     * <p>
     * 1.删除字典表,字典数据表,子字典表,子字典数据表数据<br>
     * 2.删除字典缓存<br>
     *
     * @param dictId   字典id
     * @param dictCode 字典编码
     * @author 沈家文
     * @since 2023/5/11 17:18
     */
    public void deleteDictById(Long dictId, String dictCode) {
        sysDictDao.deleteById(dictId);
        sysDictDataDao.delete(Wrappers.<SysDictData>lambdaQuery().eq(SysDictData::getDictId, dictId));
        sysChildDictDao.delete(Wrappers.<SysChildDict>lambdaQuery().eq(SysChildDict::getDictId, dictId));
        sysChildDictDataDao.delete(Wrappers.<SysChildDictData>lambdaQuery().eq(SysChildDictData::getDictId, dictId));
        deleteDictCache(dictCode);
    }

    /**
     * 获取字典读写锁种的写锁
     * <p> 对单个字典缓存加锁
     *
     * @param dictCode 字典编码
     * @return 已经锁定的写锁, 过期时间1分钟
     * @author 沈家文
     * @since 2023/5/10 14:39
     */
    @SneakyThrows
    public RLock getLoadDictCacheWriteLock(String dictCode) {
        // 加锁,过期时间一分钟
        RReadWriteLock readWriteLock = redissonClient.getReadWriteLock(LOCK_DICT_CACHE_KEY + dictCode);
        RLock lock = readWriteLock.writeLock();
        if (lock.isLocked()) {
            if (!lock.tryLock(1, TimeUnit.MINUTES)) {
                throw new BaseException("获取字典缓存加载锁失败 dict:" + dictCode);
            }
        }
        lock.lock(1, TimeUnit.MINUTES);
        return lock;
    }

    /**
     * 获取字典读写锁种的读锁
     * <p> 对单个字典缓存加锁
     *
     * @param dictCode 字典编码
     * @return 已经锁定的读锁, 过期时间1分钟
     * @author 沈家文
     * @since 2023/5/10 14:37
     */
    @SneakyThrows
    public RLock getLoadDicCachetReadLock(String dictCode) {
        // 加锁,过期时间一分钟
        RReadWriteLock readWriteLock = redissonClient.getReadWriteLock(LOCK_DICT_CACHE_KEY + dictCode);
        RLock lock = readWriteLock.readLock();
        if (lock.isLocked()) {
            if (!lock.tryLock(1, TimeUnit.MINUTES)) {
                throw new BaseException("获取字典缓存加载锁失败 dict:" + dictCode);
            }
        }
        lock.lock(1, TimeUnit.MINUTES);
        return lock;
    }

    @SneakyThrows
    private String dictToCacheValue(DictCache dictCache) {
        return objectMapper.writeValueAsString(ApiResult.success(dictCache));
    }

}
